package com.derekandbritt.booST;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;

import com.derekandbritt.booST.data.Challenge;
import com.derekandbritt.booST.data.Challenge.Type;

public class ChallengeService extends Service {

    public final static String CHALLENGE_DISTANCE_KEY = "com.derekandbritt.booST.challengeDistance";
    public final static String CHALLENGE_TYPE_KEY = "com.derekandbritt.booST.challengeType";
    public final static String CHALLENGE_TIME_LIMIT_KEY = "com.derekandbritt.booST.challengeTimeLimit";
    
	private CountDownTimer cooperativeTimer;
	private static Object timeLock = new Object();

	private long challengeId; 
	private Challenge.Type challengeType;
	private int challengeTimeLimit;
	private double challengeDistance; 
	
	private long timeElapsed = 0;
    private long feetCovered = 0;
	private long timeRemaining;
	private long startTime;
	private boolean challengeComplete;
	
	private double currentLatitude = 0;
	private double currentLongitude = 0;
	private volatile Looper gpsLooper;
	
	private final Handler handler = new Handler();
    private final Runnable updateCompetitiveTimer = new Runnable() {
		   public void run() {
			   updateCompetitiveTimer();
		   }
    };
    
    // This is the object that receives interactions from clients.  See
    // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();
    
    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
        ChallengeService getService() {
            return ChallengeService.this;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public void onStart(Intent intent, int startId) {
        
        // TODO check to see that the challengeId does not already exist
        Type type = Type.valueOf(intent.getExtras().getString(CHALLENGE_TYPE_KEY));
        int timeLimit = intent.getExtras().getInt(CHALLENGE_TIME_LIMIT_KEY, 0);
        double distance = intent.getExtras().getDouble(CHALLENGE_DISTANCE_KEY);
        
        start(type, timeLimit, distance);
    }
    
    @Override
    public void onCreate() { }

    @Override
    public void onDestroy() {
        completeChallenge();
    }

    public void start(Challenge challenge) {
     
        if(challenge.getId() == challengeId) {
            //TODO log that it has already been started
            return;
        }
        
        challengeId = challenge.getId();
        start(challenge.getType(),
              challenge.getTimeLimit(),
              challenge.getDistance());
    }
    
    private void start(Type type, int timeLimit, double distance) {
        challengeComplete = false;
        challengeType = type;
        challengeTimeLimit = timeLimit;
        challengeDistance = distance;
        
        if(challengeType == Challenge.Type.COMPETITIVE) {
            setupCompetitiveTimer();
        }
        else if(challengeType == Challenge.Type.COOPERATIVE) {
            setupCooperativeTimer();
        }
        
        setupDistanceMetrics();
        
    }
    
    public void completeChallenge() {
        // stop the timer and distance measurements
        challengeComplete = true;
        gpsLooper.quit();
        if(cooperativeTimer != null)
            cooperativeTimer.cancel();
        else
            handler.removeCallbacks(updateCompetitiveTimer);
    }
    
	private void setupCooperativeTimer() {
		
	    if(cooperativeTimer != null)
	        cooperativeTimer.cancel();
	    
		cooperativeTimer = new CountDownTimer(challengeTimeLimit, 1000) {
		
			public void onTick(long millisUntilFinished) {
				synchronized(timeLock) { 
				    timeElapsed = challengeTimeLimit - millisUntilFinished;
				    timeRemaining = millisUntilFinished;
				}
			}
		
			public void onFinish() {
				synchronized(timeLock) { 
					timeElapsed = challengeTimeLimit + 1;
					timeRemaining = 0;
					completeChallenge();
				}
			}
		}.start();	
	}

	private void setupCompetitiveTimer() {
		startTime = System.currentTimeMillis();
		handler.removeCallbacks(updateCompetitiveTimer);
		handler.postDelayed(updateCompetitiveTimer, 1000);
	}
	
	private void updateCompetitiveTimer() {
		synchronized(timeLock) { timeElapsed = System.currentTimeMillis() - startTime; }
		handler.postDelayed(updateCompetitiveTimer, 1000);
	}
	
	private void setupDistanceMetrics() {
		
    	Log.i("com.derekandbritt.booST", "Starting up the booST Distance Metrics");
    	
    	HandlerThread thread = new HandlerThread("booST_GPS_Service");
        thread.start();
        gpsLooper = thread.getLooper();
		
        GPSListener listener = new GPSListener();
        
    	LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    	locationManager.removeUpdates(listener);
    	locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 5, listener, gpsLooper);
	}
	
	private class GPSListener implements LocationListener {
		
		public void onLocationChanged(Location location) {
			Log.i("com.derekandbritt.booST", "Current Location: " + location.toString());
			
			// initialize the first time
			if(currentLatitude == 0 && currentLongitude == 0) {
				currentLatitude = location.getLatitude();
				currentLongitude = location.getLongitude();
				return;
			}
			
			// compute distance covered
			double newLatitude = location.getLatitude();
			double newLongitude = location.getLongitude();
			float[] results = new float[1];
			Location.distanceBetween(currentLatitude, currentLongitude, newLatitude, newLongitude, results);
			
			// update distance metrics
			feetCovered += results[0] * 3.2808399;
			
	        // update the current location values
            currentLatitude = newLatitude;
            currentLongitude = newLongitude;
			
			// complete the challenge if needed
			double miles = ((double)feetCovered) / 5280;
            double milesRemaining = challengeDistance - miles;
            if(milesRemaining <= 0) {
                    synchronized(timeLock) { completeChallenge(); }
            }
		}

		public void onProviderDisabled(String s) {
			Log.i("com.derekandbritt.booST", "Disabled");
		}

		public void onProviderEnabled(String s) {
			Log.i("com.derekandbritt.booST", "Enabled");
		}

		public void onStatusChanged(String s, int i, Bundle bundle) {
			Log.i("com.derekandbritt.booST", "Status Changed");	
		}
    }

    public long getTimeElapsed() {
        return timeElapsed;
    }

    public long getTimeRemaining() {
        return timeRemaining;
    }

    public long getStartTime() {
        return startTime;
    }

    public long getFeetCovered() {
        return feetCovered;
    }
    
    public boolean isChallengeComplete() {
        return challengeComplete;
    }
}
